<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    // vue实现响应式核心为两个
    // 第一个: vue如何监听data数据的更改
    // 第二个: vue如何知道应该修改页面中的哪个元素

    // 先来解决第一个问题
    // 实质上vue是通过defineProperty方法来监听数据的更改的
    // 比如这里的obj对象就是vue中的data对象
    const obj = {
      name: "xiaolam",
      age: 18
    }

    // vue中会获得这个obj对象, 然后遍历里面的键
    // 通过 Object.keys(obj) 来获取obj中的键并组成一个数组
    // 然后通过forEach 遍历这个数组, 给数组中的键通过defineProperty方法监听里面的所有key
    Object.keys(obj).forEach(key => {
      let value = obj[key];
      Object.defineProperty(obj, key, {
        // 通过defineProperty中的set方法就可以监听obj对象中数据的更改了
        set(newValue) {
          console.log("进入defineProperty中的set");

          // 在更改obj中的数据的时候, 调用发布者中的notify方法, 这个方法会调用所有订阅者中的update方法
          // 而update方法里面可以编写更新页面的代码
          dep.notify()

          value = newValue
        },
        // 通过defineProperty中的get方法就可以监听页面中什么元素的通过mustache语法取用obj数据
        get() {
          console.log("进入defineProperty中的get");

          // 在使用obj的数据后, 将使用数据的元素做一个记录
          // 存储在发布者中
          const w1 = new Watcher("w1")
          dep.addSub(w1)

          return value
        }
      })
    })
    // 通过上面的过程就可以实现第一个问题: vue如何实现监听data数据的更改

    // 现在解决第二个问题: vue如何知道应该修改页面中的哪个元素
    // 实质上vue使用的是一个 发布者订阅者 的技术
    // 发布者就是指vue实例
    // 而订阅者就是指使用mustache语法的元素

    // 发布者
    class Dep {
      constructor() {
        // 这个数组用于存储订阅者
        this.subs = []
      };

      // 在元素取用data数据的时候调用Dep中的这个方法, 来将元素(订阅者)加入到subs数组中用于记录订阅者都有谁
      addSub(watcher) {
        this.subs.push(watcher)
      };

      notify() {
        this.subs.forEach(item => {
          item.update()
        })
      }
    }

    // 订阅者
    class Watcher {
      constructor(name) {
        this.name = name
      };
      update() {
        // 在这里做更新页面的操作
        console.log("更新");
      }
    }
    const dep = new Dep()
  </script>
</body>

</html>